package com.cskaoyan.javase.object._4hashcode;

/**
 * 方法的声明
 * hashCode()方法的方法声明：
 * public native int hashCode();
 * 很显然，它是一个本地方法，这个方法也没有任何参数，返回值是一个int类型整数。
 *
 * 方法的作用
 * 由于它是一个本地方法
 * 我们通过查阅JDK文档，文档中关于它的作用说明了两点：
 *      1.返回该对象的哈希码值。
 *          这个方法的返回值就是这个对象的哈希值.
 *
 *      2.支持此方法是为了提高哈希表
 *      （例如 java.util.Hashtable 提供的哈希表）的性能。
 *  关于哈希表、哈希码值等相关的概念，如果你对数据结构这门课程有些了解，那么这些概念就是非常容易的。当然如果你还不太明白，就看一下下面的说明。
 *
 *  解释一下以下概念:
 *  映射: 允许出现多对一,但绝不可能出现一对多
 *      函数是一种特殊的映射,要求映射中的A集合B集合都是数集
 *      比如一个函数f(x),f(1),f(2),f(3),它们的结果可以完全相同,但是f(1)的取值必须固定,不可能有多个取值
 *  哈希: 也是一种特殊的映射,但是要求集合A是一个无限大小,无穷多元素的集合
 *          集合B是一个有限的集合
 *          化无限为有限,这就是哈希
 *  哈希值: 无限大小集合A中的某个元素,唯一的映射集合B中的某个元素,集合B上的这个元素就是哈希值
 *  哈希算法,哈希方法,哈希函数: 指的是哈希映射当中,集合A和集合B之间的对应关系.
 *  Java当中的哈希映射:
 *      Java当中的哈希方法就是Object类当中的hashCode方法,它被每一个类继承
 *      所以在Java当中,哈希映射指的是:
 *      某个类的对象来调用hashCode方法,所以某个类的全体对象就是哈希映射当中的集合A
 *      某个类的全体对象从理论上来说是无限个的
 *      在这个哈希映射中,集合B就是hashCode方法返回值的类型-->int数据类型(int有固定的取值范围,是一个有限大小的集合)
 *      这个方法的返回值,一个int整数,就是这个对象的哈希值(哈希码值)
 *      hashCode方法的具体实现,就是这个哈希映射当中的哈希算法
 *      比如,对于一个String类来说,给出以下哈希映射的规则:
 *          一个字符串对象,它的字符串长度是多少,它的哈希值就是多少
 *          "123" --> 3
 *          "1234" --> 4
 *          "abc" --> 3
 *          ...
 *      以上就是一次哈希映射,允许出现多对一,但绝不可能一对多
 *
 *  哈希表:
 *  哈希表在Java当中的实现,本质上是一个对象数组
 *      哈希表是有下标的,键值对对象的存储下标(存储位置)是由key对象的哈希值决定的
 *      这样哈希表的存储都可以利用数组的下标来实现随机存取,效率很高
 *      但是key对象计算哈希值时,虽然不可能出现一对多,但可能出现多对一,这种情况下哈希值一样
 *      被称之为"哈希冲突"
 *      一旦出现哈希冲突,就会导致数组一个存储单元,存储多个键值对,这显然不可能
 *      所以采取"拉链法"将这些多个键值对"挂在数组的下面",链表
 *
 *      在哈希表当中,为了效率更好,链表当中的键值对肯定是越少越好的
 *      -->
 *      哈希冲突,越少越好(不同对象的哈希值不同,尽量少出现不同对象,哈希值相同的情况)
 *      --> 如何减少哈希冲突呢?
 *      要设计出更优秀的哈希算法,来减少哈希冲突,提高哈希表的性能
 *
 * 在Java当中,只要使用哈希表,必然涉及hashCode方法,必须好好设计该方法,减少哈希冲突,提高哈希表性能.
 *
 * 接下来,还有最后一个问题,为什么equals方法和hashCode方法要一起重写呢?
 * 哈希表存储键值对,key是唯一性标识,不能重复的,key对象是不能重复的
 * 那么对于一个已存储键值对的哈希表来说,如何判断key是否重复呢?
 * 用该类的equals方法
 * 如果该类的equals方法判断执行的结果是true,表示对象相等,key重复了,反之则key不重复
 *
 * 在哈希表当中,一定会出现哈希冲突,这时会将键值对挂起来
 * 那么挂起来以后,如何去查找到目标键值对呢?
 * 遍历对象数组,逐一比对查找key,找到对应的key,就找到对应的value
 *
 * 在哈希表插入元素时,
 * 如果key对象的equals方法判断的结果是true,表示key重复了,这时再进行插入就必须要覆盖掉原先的元素
 * 既然是覆盖,存储的位置是不能变的
 * --> 这时两个对象的哈希值必须是一样的,否则就会出现key重复,equals方法认为要覆盖
 * 但hashCode方法返回值不同,哈希值不同,要存储在其它位置的矛盾.
 * 而且,equals方法认定对象相等时,这两个对象实际上就相当于映射当中集合A里的同一个元素,映射不可能存储一对多
 *      所以:
 *      equals方法认定对象相等时,它们的hashCode方法的返回值必须是一样的
 *
 * 如果key对象的equals方法判断的结果是false,表示key不重复,开始进入插入
 *      这时:
 *          a.它们的哈希值可能是一样的,这就是哈希冲突,这时就需要采取链表的形式将这个键值对挂起来
 *          b.它们的哈希值也可能不同,这时它们的存储位置就不同
 *      当然: 要设计出更好的哈希算法,减少哈希冲突,提升性能
 *      equals方法认定对象不相等时,它们的hashCode方法的返回值没有要求,但是要记住,如果哈希值不同,哈希表的性能就会更好.
 *
 * 最后:
 *      equals方法返回true --> hashCode方法返回值必须一样
 *      单单这一点,就已经要求这两个方法必须同时重写了
 *      不仅要求同时重写,而且还要求这两个方法重写的规则也必须一样
 *      比如Student类,重写equals方法,比较的规则是比较属性age,score的取值
 *      那么hashCode方法也必须按照属性的取值age,score来计算哈希值
 *
 * 当然具体的重写,就是需要设计哈希算法,这不是Java程序员应该做的事情
 * 一般情况下,使用idea的模板来生成hashCode方法就够了
 *
 * 注意事项:
 *      一旦重写hashCode方法后，默认的toString方法就不会再打印地址值了。
 *      这是因为toString方法，默认调用的是本地方法的hashCode方法获取地址值，重写后不再是地址值了。
 *      建议重写hashCode方法的同时,顺便重写toString
 *
 *
 * @since 15:40
 * @author wuguidong@cskaoyan.onaliyun.com
 */
public class Demo {
    public static void main(String[] args) {
        Student s1 = new Student(18, 100);
        Student s2 = new Student(18, 100);
        System.out.println(s1.equals(s2));
        System.out.println(s1.hashCode());
        System.out.println(s2.hashCode());
        Student s3 = s1;
        System.out.println(s1.equals(s3));
        System.out.println(s1.hashCode());
        System.out.println(s3.hashCode());

        System.out.println(s1);
        System.out.println(s2);
    }
}

class Student {
    int age;
    double score;

    public Student(int age, double score) {
        this.age = age;
        this.score = score;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Student student = (Student) o;

        if (age != student.age) return false;
        return Double.compare(student.score, score) == 0;
    }

    @Override
    public int hashCode() {
        int result;
        long temp;
        result = age;
        temp = Double.doubleToLongBits(score);
        result = 31 * result + (int) (temp ^ (temp >>> 32));
        return result;
        // return (int) (age + score);
    }

    @Override
    public String toString() {
        return "Student{" +
                "age=" + age +
                ", score=" + score +
                '}';
    }
}